package com.cmes.system.core.service;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.cmes.core.constant.enums.EnableStatus;
import com.cmes.core.utils.CollectionUtils;
import com.cmes.core.utils.MathUtils;
import com.cmes.core.utils.Preconditions;
import com.cmes.jdbc.service.GenericService;
import com.cmes.system.core.constant.DeptTypeEnum;
import com.cmes.system.core.constant.ErrorCode;
import com.cmes.system.core.entity.DeptEntity;
import com.cmes.system.core.entity.UserDeptEntity;
import com.cmes.system.core.repository.DeptRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 部门管理
 *
 * @author Bruce.Gong
 * @since 1.0.0-SNAPSHOT
 */
@Slf4j
@RequiredArgsConstructor
@Service
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
public class DeptService extends GenericService<DeptRepository, DeptEntity> {

    private final UserDeptService userDeptService;

    /**
     * 查询所有部门，以树结构返回
     *
     * @return 部门树
     */
    public List<DeptEntity> tree() {
        return CollectionUtils.listTree(list(),
                0L,
                DeptEntity::getId,
                DeptEntity::getPid,
                DeptEntity::setChildren,
                null,
                Comparator.comparing(DeptEntity::getPid).thenComparing(DeptEntity::getSort));
    }

    // region 页面操作，增删查改

    /**
     * 校验新增功能
     *
     * @param entity 前端接收对象
     */
    public void canAdd(DeptEntity entity) {
        // 检查名称是否唯一
        checkName(entity);
        // 检查上级部门是否可用
        checkParentActive(entity.getPid());
    }

    /**
     * 判断是否可修改
     *
     * @param entity 部门
     */
    public void canUpdate(DeptEntity entity) {
        DeptEntity db = checkActive(entity.getId());
        // 父部门发生变化，检查父部门状态，检查是否有子部门
        boolean parentChanged = !MathUtils.equals(db.getPid(), entity.getPid());
        if (parentChanged) {
            checkParentActive(entity.getPid());
        }
        boolean typeChanged = !MathUtils.equals(db.getType(), entity.getType());
        if (typeChanged && DeptTypeEnum.PERSONAL.match(entity.getType())) {
            checkHasChildren(db.getId());
        }
        boolean nameChanged = !Objects.equals(db.getName(), entity.getName());
        if (nameChanged) {
            checkName(entity);
        }
    }

    /**
     * 判断是否可删除
     *
     * @param ids 部门ID
     * @return 部门信息
     */
    public Set<Long> canDelete(List<Long> ids) {
        // 向下检索所有的部门
        Set<Long> mergedIds = routerChildren(ids, null);
        // 检查是否有成员
        checkHasMember(mergedIds, "删除");
        return mergedIds;
    }

    /**
     * 删除部门，同步删除部门下的用户
     *
     * @param ids 部门ID
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void deleteDept(Set<Long> ids) {
        deleteBatch(ids);
        userDeptService.deleteByDeptIds(ids);
    }

    /**
     * 判断是否可停用
     *
     * @param ids 部门ID
     * @return 需要作废的部门ID
     */
    public Set<Long> canDisable(List<Long> ids) {
        // 向下检索所有的部门
        Set<Long> mergedIds = routerChildren(ids, EnableStatus.ENABLE);
        // 检查是否有成员
        checkHasMember(mergedIds, "停用");
        return mergedIds;
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void disable(Set<Long> ids) {
        update(Wrappers.<DeptEntity>lambdaUpdate()
                .set(DeptEntity::getStatus, EnableStatus.DISABLE.getCode())
                .in(DeptEntity::getId, ids)
                .eq(DeptEntity::getStatus, EnableStatus.ENABLE.getCode()));
    }

    /**
     * 判断是否可启用
     *
     * @param ids 部门ID
     * @return 需要启用的部门ID
     */
    public Set<Long> canEnable(List<Long> ids) {
        // 向上检索所有的部门
        return routerParent(ids);
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void enable(Set<Long> ids) {
        update(Wrappers.<DeptEntity>lambdaUpdate()
                .set(DeptEntity::getStatus, EnableStatus.ENABLE.getCode())
                .in(DeptEntity::getId, ids)
                .eq(DeptEntity::getStatus, EnableStatus.DISABLE.getCode()));
    }

    /**
     * 校验部门是否存在
     *
     * @return 部门信息
     */
    private DeptEntity checkExists(Long id) {
        DeptEntity db = getById(id);
        Preconditions.isNull(db, ErrorCode.DEPT_NOT_EXISTS);
        return db;
    }

    /**
     * 校验部门状态
     * - 部门是否存在
     * - 部门状态是否可用
     *
     * @return 部门信息
     */
    private DeptEntity checkActive(Long id) {
        DeptEntity db = checkExists(id);
        Preconditions.check(() -> EnableStatus.DISABLE.match(db.getStatus()), ErrorCode.DEPT_DISABLED);
        return db;
    }

    /**
     * 校验部门名称是否唯一
     *
     * @param entity {id,pid,name}
     */
    private void checkName(DeptEntity entity) {
        Preconditions.check(() -> exists(Wrappers.<DeptEntity>lambdaQuery()
                .eq(DeptEntity::getPid, entity.getPid())
                .eq(DeptEntity::getName, entity.getName())), ErrorCode.DEPT_NAME_EXISTS);
    }

    /**
     * 校验部门是否存在
     */
    private void checkParentActive(Long id) {
        if (id == null || id.compareTo(0L) == 0) {
            return;
        }
        DeptEntity db = getById(id);
        Preconditions.isNull(db, ErrorCode.DEPT_PARENT_NOT_EXISTS);
        Preconditions.check(() -> EnableStatus.DISABLE.match(db.getStatus()), ErrorCode.DEPT_PARENT_DISABLED);
        Preconditions.check(() -> DeptTypeEnum.PERSONAL.match(db.getType()), ErrorCode.DEPT_PARENT_PERSONAL);
    }

    /**
     * 检查是否存在子部门，如果存在子部门，则不能调整父部门，也不能删除或者停用
     *
     * @param id 部门ID
     */
    private void checkHasChildren(Long id) {
        Preconditions.check(() -> exists(Wrappers.<DeptEntity>lambdaQuery()
                .eq(DeptEntity::getPid, id)
                .eq(DeptEntity::getStatus, EnableStatus.ENABLE.getCode())),
                ErrorCode.DEPT_HAS_CHILDREN);
    }

    /**
     * 检查部门存在成员
     *
     * @param ids 部门ID
     */
    private void checkHasMember(Collection<Long> ids, String operate) {
        Preconditions.check(() -> userDeptService.exists(Wrappers.<UserDeptEntity>lambdaQuery()
                .in(UserDeptEntity::getDeptId, ids)), ErrorCode.DEPT_HAS_MEMBER, operate);
    }

    /**
     * 向下检索所有的部门
     *
     * @param ids 上级部门
     * @return 上级部门的所有下级部门
     */
    private Set<Long> routerChildren(List<Long> ids, EnableStatus status) {
        List<DeptEntity> all = checkStatus(ids, status, ErrorCode.DEPTS_DISABLED, (l, m) -> l);
        Set<Long> mergedIds = new HashSet<>(ids);
        Map<Long, List<DeptEntity>> group = all.stream().collect(Collectors.groupingBy(DeptEntity::getPid));
        for (Long id : ids) {
            routerChildren(group, id, status, mergedIds);
        }
        return mergedIds;
    }

    private void routerChildren(Map<Long, List<DeptEntity>> map, Long id, EnableStatus status, Set<Long> mergedIds) {
        List<DeptEntity> children = map.get(id);
        if (!CollectionUtils.isEmpty(children)) {
            for (DeptEntity child : children) {
                if (status != null) {
                    if (status.match(child.getStatus())) {
                        mergedIds.add(child.getId());
                        routerChildren(map, child.getId(), status, mergedIds);
                    }
                } else {
                    mergedIds.add(child.getId());
                    routerChildren(map, child.getId(), status, mergedIds);
                }
            }
        }
    }

    /**
     * 向上检索所有的部门
     *
     * @param ids 下级部门
     * @return 下级部门的所有上级部门
     */
    private Set<Long> routerParent(List<Long> ids) {
        Map<Long, DeptEntity> map = checkStatus(ids, EnableStatus.DISABLE, ErrorCode.PERMS_ENABLED, (l, m) -> m);
        Set<Long> mergedIds = new HashSet<>(ids);
        for (Long id : ids) {
            routerParent(map, map.get(id), EnableStatus.DISABLE, mergedIds);
        }
        return mergedIds;
    }

    private void routerParent(Map<Long, DeptEntity> map, DeptEntity dept, EnableStatus status, Set<Long> mergedIds) {
        if (dept != null && dept.getPid() > 0L) {
            DeptEntity parent = map.get(dept.getPid());
            Preconditions.isNull(parent, ErrorCode.DEPT_PARENT_NOT_EXISTS);
            if (status != null) {
                if (status.match(parent.getStatus())) {
                    mergedIds.add(parent.getId());
                    routerParent(map, parent, status, mergedIds);
                }
            } else {
                mergedIds.add(parent.getId());
                routerParent(map, parent, status, mergedIds);
            }
        }
    }

    private <T> T checkStatus(List<Long> ids, EnableStatus status, ErrorCode errorCode,
                              BiFunction<List<DeptEntity>, Map<Long, DeptEntity>, T> func) {
        List<DeptEntity> all = list();
        Map<Long, DeptEntity> map = all.stream().collect(Collectors.toMap(DeptEntity::getId, Function.identity()));
        for (Long id : ids) {
            DeptEntity current = map.get(id);
            Preconditions.isNull(current, ErrorCode.DEPT_NOT_EXISTS);
            if (status != null) {
                Preconditions.check(() -> !status.match(current.getStatus()), errorCode);
            }
        }
        return func.apply(all, map);
    }

    // endregion

}
